package cn.stimd.spring.aop.framework.adapter;

import cn.stimd.spring.aop.AfterAdvice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 异常通知拦截器比较特殊，并不需要一个ThrowsAdvice接口的实现类，可以是任意对象，且拥有一个方法签名如下的异常处理方法：
 * afterThrowing(Method method, Object[] args, Object target, Exception ex)
 * afterThrowing(Exception ex)
 * <p>
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
    private static final String AFTER_THROWING = "afterThrowing";
    private final Object throwsAdvice;
    private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();

    public ThrowsAdviceInterceptor(Object throwsAdvice) {
        this.throwsAdvice = throwsAdvice;

        for (Method method : throwsAdvice.getClass().getDeclaredMethods()) {
            if (method.getName().equals(AFTER_THROWING)) {

                //获取afterThrowing方法参数，固定为1个或4个
                Class<?>[] paramTypes = method.getParameterTypes();
                if (paramTypes.length == 1 || paramTypes.length == 4) {
                    Class<?> throwableParam = paramTypes[paramTypes.length - 1];    //最后一个参数是异常类型
                    if (Throwable.class.isAssignableFrom(throwableParam)) {
                        this.exceptionHandlerMap.put(throwableParam, method);
                    }
                }
            }
        }
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }catch (Throwable ex){
            //获取异常处理方法
            Method handlerMethod = getExceptionHandler(ex);
            if (handlerMethod != null) {
                invokeHandlerMethod(mi, ex, handlerMethod);
            }
            throw ex;
        }
    }


    private Method getExceptionHandler(Throwable exception) {
        Class<?> exceptionClass = exception.getClass();

        Method handler = this.exceptionHandlerMap.get(exceptionClass);
        while (handler == null && exceptionClass != Throwable.class) {
            exceptionClass = exceptionClass.getSuperclass();
            handler = this.exceptionHandlerMap.get(exceptionClass);
        }
        return handler;
    }


    //通过反射调用异常处理方法
    private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
        Object[] handlerArgs;
        //单个参数方法
        if (method.getParameterTypes().length == 1) {
            handlerArgs = new Object[] {ex};
        }
        //四个参数方法
        else {
            handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
        }
        try {
            //执行异常处理的方法
            method.invoke(this.throwsAdvice, handlerArgs);
        }
        catch (InvocationTargetException targetEx) {
            throw targetEx.getTargetException();
        }
    }

}
